该内容已被发布者删除 该内容被自由微信恢复
文章于 2021年9月8日 被检测为删除。
查看原文
被用户删除
其他

作为Lombok的老司机,我这次翻车了,啪啪打脸~

点击关注 👉 鸭哥聊Java 2021-09-08

大家好,我是鸭哥。


下面开始我们的正文。去年在某项目当中引入了 Lombok 插件,着实解放了双

手,代替了一些重复的简单工作(Getter,Setter,toString 等方法的编写)。


但是,在使用的过程当中,也发现了一些坑,开始的时候并没有察觉到是

Lombok 的问题,后来跟踪了对应的其他组件的源码,才发现是 Lombok 的问

题!

Setter-Getter 方法的坑


问题发现


我们在项目当中主要使用 Lombok 的 Setter-Getter 方法的注解,也就是组合注

解 @Data,但是在一次使用 Mybatis 插入数据的过程当中,出现了一个问题。


问题描述如下:
我们有个实体类:@Datapublic class NMetaVerify{ private NMetaType nMetaType; private Long id; ....其他属性}


当我们使用 Mybatis 插入数据的时候,发现,其他属性都能正常的插入,但是

就是 nMetaType 属性在数据库一直是 null。


解决

当我 Debug 项目代码到调用 Mybatis 的插入 SQL 对应的方法的时候,我

看到 NMetaVerify 对象的 nMetaType 属性还是有数据的,但是执行插入

之后,数据库的 nMetaType 字段就是一直是 null。


原先我以为是我的枚举类型写法不正确,看了下别的同样具有枚举类型的字段,

也是正常能插入到数据库当中的,这更让我感觉到疑惑了。


于是,我就跟踪 Mybatis 的源码,发现 Mybatis 在获取这个

nMetaType 属性的时候使用了反射,使用的是 getxxxx 方法来获取的。


但是我发现 nMetaType 的 get 方法好像有点和 Mybatis 需要的

getxxxx 方法长的好像不一样,问题找到了!

原因


Lombok 对于第一个字母小写,第二个字母大写的属性生成的 get-set 方法和
Mybatis 以及 idea 或者说是 Java 官方认可的 get-set 方法生成的不一样:
#Lombok生成的Get-Set方法@Datapublic class NMetaVerify { private Long id; private NMetaType nMetaType; private Date createTime;
public void lombokFound(){ NMetaVerify nMetaVerify = new NMetaVerify(); nMetaVerify.setNMetaType(NMetaType.TWO); //注意:nMetaType的set方法为setNMetaType,第一个n字母大写了, nMetaVerify.getNMetaType(); //getxxxx方法也是大写 }}
#idea,Mybatis,Java官方默认的行为为:public class NMetaVerify { private Long id; private NMetaType nMetaType; private Date createTime;
public Long getId() { return id; }
public void setId(Long id) { this.id = id; }
public NMetaType getnMetaType() {//注意:nMetaType属性的第一个字母小写 return nMetaType; }
public void setnMetaType(NMetaType nMetaType) {//注意:nMetaType属性的第一个字母小写 this.nMetaType = nMetaType; }
public Date getCreateTime() { return createTime; }
public void setCreateTime(Date createTime) { this.createTime = createTime; }}

Mybatis(3.4.6 版本)解析 get-set 方法获取属性名字的源码:

package org.apache.ibatis.reflection.property;
import java.util.Locale;
import org.apache.ibatis.reflection.ReflectionException;
/** * @author Clinton Begin */public final class PropertyNamer {
private PropertyNamer() { // Prevent Instantiation of Static Class }
public static String methodToProperty(String name) { if (name.startsWith("is")) {//is开头的一般是bool类型,直接从第二个(索引)开始截取(简单粗暴) name = name.substring(2); } else if (name.startsWith("get") || name.startsWith("set")) {//set-get的就从第三个(索引)开始截取 name = name.substring(3); } else { throw new ReflectionException("Error parsing property name '" + name + "'. Didn't start with 'is', 'get' or 'set'."); } //下面这个判断很重要,可以分成两句话开始解释,解释如下 //第一句话:name.length()==1 // 对于属性只有一个字母的,例如private int x; // 对应的get-set方法是getX();setX(int x); //第二句话:name.length() > 1 && !Character.isUpperCase(name.charAt(1))) // 属性名字长度大于1,并且第二个(代码中的charAt(1),这个1是数组下标)字母是小写的 // 如果第二个char是大写的,那就直接返回name if (name.length() == 1 || (name.length() > 1 && !Character.isUpperCase(name.charAt(1)))) { name = name.substring(0, 1).toLowerCase(Locale.ENGLISH) + name.substring(1);//让属性名第一个字母小写,然后加上后面的内容 }
return name; }
public static boolean isProperty(String name) { return name.startsWith("get") || name.startsWith("set") || name.startsWith("is"); }
public static boolean isGetter(String name) { return name.startsWith("get") || name.startsWith("is"); }
public static boolean isSetter(String name) { return name.startsWith("set"); }
}


Mybatis 解析 get-set 方法为属性名字测试:

@Test public void foundPropertyNamer() { String isName = "isName"; String getName = "getName"; String getnMetaType = "getnMetaType"; String getNMetaType = "getNMetaType";
Stream.of(isName,getName,getnMetaType,getNMetaType) .forEach(methodName->System.out.println("方法名字是:"+methodName+" 属性名字:"+ PropertyNamer.methodToProperty(methodName))); }
#输出结果如下: 方法名字是:isName 属性名字:name 方法名字是:getName 属性名字:name 方法名字是:getnMetaType 属性名字:nMetaType //这个以及下面的属性第二个字母都是大写,所以直接返回name 方法名字是:getNMetaType 属性名字:NMetaType

解决方案


解决方案如下:


  • 修改属性名字,让第二个字母小写,或者说是规定所有的属性的前两个字

    母必须小写。


  • 如果数据库已经设计好,并且前后端接口对接好了,不想修改,那就专门

    为这种特殊的属性使用 idea 生成 get-set 方法。


@Accessor(chain = true)注解的问题

问题发现


在使用 easyexcel(github.com/alibaba/eas…)导出的时候,发现以前的实体类导

出都很正常,但是现在新加的实体类不正常了。


比对了发现,新加的实体类增加了 @Accessor(chain = true)注解,我们的目
的主要是方便我们链式调用 set 方法:
new UserDto().setUserName("").setAge(10).........setBirthday(new Date());

原因


easyexcel 底层使用的是 cglib 来做反射工具包的:

com.alibaba.excel.read.listener.ModelBuildEventListener 类的第130行BeanMap.create(resultModel).putAll(map);
最底层的是cglib的BeanMap的这个方法调用
abstract public Object put(Object bean, Object key, Object value);
但是 cglib 使用的是 Java 的 rt.jar 里面的一个 Introspector 这个类的方法:
# Introspector.java 第520行if (int.class.equals(argTypes[0]) && name.startsWith(GET_PREFIX)) { pd = new IndexedPropertyDescriptor(this.beanClass, name.substring(3), null, null, method, null); //下面这行判断,只获取返回值是void类型的setxxxx方法 } else if (void.class.equals(resultType) && name.startsWith(SET_PREFIX)) { // Simple setter pd = new PropertyDescriptor(this.beanClass, name.substring(3), null, method); if (throwsException(method, PropertyVetoException.class)) { pd.setConstrained(true); }}

解决方案


解决方案如下:


  • 去掉 Accessor 注解。

  • 要么就等待 easyexcel 的作者替换掉底层的 cglib 或者是其他,反正是支持

    获取返回值不是 void 的 setxxx 方法就行。

作者:liuxuzxx

来源:juejin.im/post/6881432532332576781


近期技术热文

1、炫酷!SpringBoot+Echarts实现用户访问地图可视化项目(附源码)

2、阿里面试官:SQL 语句中 left join 后用 on 还是 where,区别大吗?

3、一代天才黑客去世,曾拯救半个互联网,年仅42岁!

4、还在用 Guava Cache?它才是 Java 本地缓存之王!


点击下方公众号
回复关键字【666
领取资料


我就知道你会点赞+“在看”

: . Video Mini Program Like ,轻点两下取消赞 Wow ,轻点两下取消在看

您可能也对以下帖子感兴趣

文章有问题?点此查看未经处理的缓存